﻿// Necroz project team © <2015> <ArchLord2EMU>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Author:Sagara
// Date:2015-09-06-23:29

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using CM.Framework.Extensions;
using NLog;

namespace ArchLord2Commons.Network
{
    public class AALNetFrame
    {
        protected static readonly Logger Log = LogManager.GetLogger(typeof(AALNetFrame).Name);

        public ushort Opcode { get; internal set; }
        public bool UseCheck { get; internal set; }

        internal byte[] Payload;

        /// <summary>
        /// Return frame buffer length
        /// </summary>
        public int Length
        {
            get
            {
                if (Payload == null)
                    return 0;

                return Buffer.ByteLength(Payload);
            }
        }

        /// <summary>
        /// Return frame buffer
        /// </summary>
        public byte[] FrameBuffer
        {
            get { return Payload; }
        }

        public long ReceiveMs { get; internal set; }

        /// <param name="initialBufferSize">Preallocated buffer size</param>
        protected AALNetFrame(int initialBufferSize = 0)
        {
            Payload = new byte[initialBufferSize];
        }

        /// <param name="buffer">Initial buffer</param>
        protected AALNetFrame(byte[] buffer)
        {
            Payload = buffer;
        }

        /// <summary>
        /// Current read/write position
        /// </summary>
        public int Position
        {
            get { return _position; }
            set { _position = value; }
        }

        /// <summary>
        /// Current read/write position
        /// </summary>
        private int _position;

        /// <summary>
        /// Get 8-bit unsigned int from packet buffer
        /// Read position will not change
        /// </summary>
        /// <returns></returns>
        public byte GetInt8(int index)
        {
            return Payload[index];
        }

        /// <summary>
        /// Get 8-bit unsigned int from packet buffer
        /// Read position will be increased by 1
        /// </summary>
        /// <returns></returns>
        public byte GetInt8()
        {
            return Payload[_position++];
        }

        /// <summary>
        /// Get 8-bit signed int from packet buffer
        /// Read position will not change
        /// </summary>
        /// <returns></returns>
        public sbyte GetSInt8(int index)
        {
            return (sbyte)Payload[index];
        }

        /// <summary>
        /// Get 8-bit signed int from packet buffer
        /// Read position will be increased by 1
        /// </summary>
        /// <returns></returns>
        public sbyte GetSInt8()
        {
            return (sbyte)Payload[_position++];
        }


        /// <summary>
        /// Get 16-bit signed int from packet buffer
        /// Read position will not change
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public short GetInt16(int index)
        {
            return BitConverter.ToInt16(Payload, index);
        }


        /// <summary>
        /// Get 16-bit signed int from packet buffer
        /// Read position will be increased by 2
        /// </summary>
        /// <returns></returns>
        public short GetInt16()
        {
            var targ = BitConverter.ToInt16(Payload, _position);
            _position += 2;
            return targ;
        }

        /// <summary>
        /// Get 16-bit unsigned int from packet buffer
        /// Read position will not change
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public ushort GetUInt16(int index)
        {
            return BitConverter.ToUInt16(Payload, index);
        }


        /// <summary>
        /// Get 16-bit unsigned int from packet buffer
        /// Read position will be increased by 2
        /// </summary>
        /// <returns></returns>
        public ushort GetUInt16()
        {
            var targ = BitConverter.ToUInt16(Payload, _position);
            _position += 2;
            return targ;
        }

        /// <summary>
        /// Get 32-bit signed int from packet buffer
        /// Read position will not change
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public int GetInt32(int index)
        {
            return BitConverter.ToInt32(Payload, index);
        }

        /// <summary>
        /// Get 32-bit signed int from packet buffer
        /// Read position will be increased by 4
        /// </summary>
        /// <returns></returns>
        public int GetInt32()
        {
            var targ = BitConverter.ToInt32(Payload, _position);
            _position += 4;
            return targ;
        }

        /// <summary>
        /// Get 32-bit unsigned int from packet buffer
        /// Read position will not change
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public uint GetUInt32(int index)
        {
            return BitConverter.ToUInt32(Payload, index);
        }

        /// <summary>
        /// Get 32-bit unsigned int from packet buffer
        /// Read position will be increased by 4
        /// </summary>
        /// <returns></returns>
        public uint GetUInt32()
        {
            var targ = BitConverter.ToUInt32(Payload, _position);
            _position += 4;
            return targ;
        }

        /// <summary>
        /// Get 64-bit signed int from packet buffer
        /// Read position will not change
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public long GetInt64(int index)
        {
            return BitConverter.ToInt64(Payload, index);
        }

        /// <summary>
        /// Get 64-bit signed int from packet buffer
        /// Read position will be increased by 8
        /// </summary>
        /// <returns></returns>
        public long GetInt64()
        {
            var targ = BitConverter.ToInt64(Payload, _position);
            _position += 8;
            return targ;
        }

        /// <summary>
        /// Get 64-bit unsigned int from packet buffer
        /// Read position will not change
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public ulong GetUInt64(int index)
        {
            return BitConverter.ToUInt64(Payload, index);
        }

        /// <summary>
        /// Get 64-bit unsigned int from packet buffer
        /// Read position will be increased by 8
        /// </summary>
        /// <returns></returns>
        public ulong GetUInt64()
        {
            var targ = BitConverter.ToUInt64(Payload, _position);
            _position += 8;
            return targ;
        }

        /// <summary>
        /// Read string from source array with fixed length
        /// </summary>
        /// <param name="fixedSize"></param>
        /// <returns></returns>
        public unsafe string GetFixedString(int fixedSize)
        {
            var array = GetBytes(fixedSize);

            var builder = new StringBuilder();

            for (int i = 0; i < fixedSize / 2; i++)
            {
                fixed (byte* bts = array)
                {
                    var val = *((char*)(bts + i * 2));
                    if (val == 0)
                        break;

                    builder.Append(val);
                }

            }

            return builder.ToString();
        }

        /// <summary>
        /// Get byte array from current buffer
        /// Read position will not change
        /// </summary>
        /// <param name="index"></param>
        /// <param name="counter"></param>
        /// <returns></returns>
        public byte[] GetBytes(int index, int counter)
        {
            var result = new byte[counter];
            Buffer.BlockCopy(Payload, index, result, 0, counter);
            return result;
        }

        /// <summary>
        /// Get byte array from current buffer
        /// Read position will be increased by requested bytes counter
        /// </summary>
        /// <param name="counter"></param>
        /// <returns></returns>
        public byte[] GetBytes(int counter)
        {
            var result = GetBytes(_position, counter);
            _position += counter;

            return result;
        }

        /// <summary>
        /// Get tructure from buffer by the directly memory copying
        /// WARNING! Struct layout must be sequential, packing equal 1
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T GetStruct<T>(int index) where T : struct
        {
            var str = new T();
            var len = Marshal.SizeOf<T>();

            if (index < 0 || index + len > Buffer.ByteLength(Payload))
                throw new ArgumentException("Index lesser than zero or structure length is bigger than requested buffer");

            IntPtr pnt = Marshal.AllocHGlobal(len);

            try
            {
                Marshal.StructureToPtr(str, pnt, false);
                Marshal.Copy(Payload, index, pnt, len);

                str = Marshal.PtrToStructure<T>(pnt);
            }
            finally
            {
                Marshal.FreeHGlobal(pnt);
            }

            return str;
        }

        /// <summary>
        /// Get structure from buffer by the directly memory copying
        /// Write position increased by struct len
        /// WARNING! Struct layout must be sequential, packing equal 1
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T GetStruct<T>() where T : struct
        {
            var len = Marshal.SizeOf<T>();
            var target = GetStruct<T>(_position);
            _position += len;

            return target;
        }


        /// <summary>
        /// Puts 8-bit unsigned integer into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public void PutInt8(int index, byte value)
        {
            ResizeIf2Small(ref Payload, index + 1);
            Payload[index] = value;

        }

        /// <summary>
        /// Puts 8-bit unsigned integer into packet buffer
        /// Write position will be increased by 1
        /// </summary>
        /// <param name="value"></param>
        public void PutInt8(byte value)
        {
            PutInt8(_position, value);
            _position++;
        }

        /// <summary>
        /// Puts 8-bit signed integer into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public void PutSInt8(int index, sbyte value)
        {
            ResizeIf2Small(ref Payload, index + 1);
            Payload[index] = (byte)value;

        }

        /// <summary>
        /// Puts 8-bit signed integer into packet buffer
        /// Write position will be increased by 1
        /// </summary>
        /// <param name="value"></param>
        public void PutSInt8(sbyte value)
        {
            PutSInt8(_position, value);
            _position++;
        }

        /// <summary>
        /// Puts 16-bit unsigned integer into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public unsafe void PutUInt16(int index, ushort value)
        {
            ResizeIf2Small(ref Payload, index + 2);

            fixed (byte* bts = Payload)
                *((ushort*)(bts + index)) = value;

        }

        /// <summary>
        /// Puts 16-bit unsigned integer into packet buffer
        /// Write position will be increased by 2
        /// </summary>
        /// <param name="value"></param>
        public void PutUInt16(ushort value)
        {
            PutUInt16(_position, value);
            _position += 2;
        }

        /// <summary>
        /// Puts 16-bit signed integer into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public unsafe void PutInt16(int index, short value)
        {
            ResizeIf2Small(ref Payload, index + 2);

            fixed (byte* bts = Payload)
                *((short*)(bts + index)) = value;
        }

        /// <summary>
        /// Puts 16-bit signed integer into packet buffer
        /// Write position will be increased by 2
        /// </summary>
        /// <param name="value"></param>
        public void PutInt16(short value)
        {
            PutInt16(_position, value);
            _position += 2;
        }
        /// <summary>
        /// Puts 16-bit signed integer into packet buffer
        /// Write position will be increased by 2
        /// </summary>
        /// <param name="value"></param>
        public void PutInt16(int value)
        {
            PutInt16(_position, (short)value);
            _position += 2;
        }
        /// <summary>
        /// Puts 32-bit signed integer into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public unsafe void PutInt32(int index, int value)
        {
            ResizeIf2Small(ref Payload, index + 4);

            fixed (byte* bts = Payload)
                *((int*)(bts + index)) = value;
        }

        /// <summary>
        /// Puts 32-bit signed integer into packet buffer
        /// Write position will be increased by 4
        /// </summary>
        /// <param name="value"></param>
        public void PutInt32(int value)
        {
            PutInt32(_position, value);
            _position += 4;
        }

        public void PutFloat(float value)
        {
            PutFloat(_position, value);
            _position += 4;
        }

        /// <summary>
        /// Puts 32-bit signed single into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public unsafe void PutFloat(int index, float value)
        {
            ResizeIf2Small(ref Payload, index + 4);

            fixed (byte* bts = Payload)
                *((float*)(bts + index)) = value;
        }
        /// <summary>
        /// Puts 32-bit unsigned integer into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public unsafe void PutUInt32(int index, uint value)
        {
            ResizeIf2Small(ref Payload, index + 4);

            fixed (byte* bts = Payload)
                *((uint*)(bts + index)) = value;
        }

        /// <summary>
        /// Puts 32-bit unsigned integer into packet buffer
        /// Write position will be increased by 4
        /// </summary>
        /// <param name="value"></param>
        public void PutUInt32(uint value)
        {
            PutUInt32(_position, value);
            _position += 4;
        }

        /// <summary>
        /// Puts 64-bit unsigned integer into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public unsafe void PutUInt64(int index, ulong value)
        {
            ResizeIf2Small(ref Payload, index + 8);

            fixed (byte* bts = Payload)
                *((ulong*)(bts + index)) = value;
        }

        /// <summary>
        /// Puts 64-bit unsigned integer into packet buffer
        /// Write position will be increased by 8
        /// </summary>
        /// <param name="value"></param>
        public void PutUInt64(ulong value)
        {
            PutUInt64(_position, value);
            _position += 8;
        }

        /// <summary>
        /// Puts 64-bit signed integer into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public unsafe void PutInt64(int index, long value)
        {
            ResizeIf2Small(ref Payload, index + 8);

            fixed (byte* bts = Payload)
                *((long*)(bts + index)) = value;
        }

        /// <summary>
        /// Puts 64-bit signed integer into packet buffer
        /// Write position will be increased by 8
        /// </summary>
        /// <param name="value"></param>
        public void PutInt64(long value)
        {
            PutInt64(_position, value);
            _position += 8;
        }

        /// <summary>
        /// Puts byte array into packet buffer
        /// Write position will not change
        /// </summary>
        /// <param name="index">Target offset</param>
        /// <param name="value"></param>
        public void PutArray(int index, byte[] value)
        {
            var len = Buffer.ByteLength(value);

            ResizeIf2Small(ref Payload, index + len);

            Buffer.BlockCopy(value, 0, Payload, index, len);
        }

        /// <summary>
        /// Puts byte array into packet buffer
        /// Write position will be increased by source array size
        /// </summary>
        /// <param name="value"></param>
        public void PutArray(byte[] value)
        {
            PutArray(_position, value);
            _position += Buffer.ByteLength(value);
        }

        /// <summary>
        /// Puts byte array into packet buffer
        /// Write position will be increased by source array size
        /// </summary>
        public void PutArray(string hex)
        {
            var bytes = hex.ToBytes();

            PutArray(bytes);
        }
        /// <summary>
        /// Puts null terminated string into packet buffer
        /// </summary>
        public void PutString(int index, string str, Encoding encoding)
        {
            var array = new byte[(encoding.IsSingleByte ? str.Length : str.Length * 2) + 2];
            var targ = encoding.GetBytes(str);
            Buffer.BlockCopy(targ, 0, array, 0, Buffer.ByteLength(targ));

            PutArray(index, array);
        }

        /// <summary>
        /// Puts null terminated unicode string into packet buffer
        /// </summary>
        public void PutString(int index, string str)
        {
            PutString(index, str, Encoding.Unicode);
            _position += str.Length * 2 + 2;
        }

        /// <summary>
        /// Puts null terminated unicode string into packet buffer
        /// </summary>
        public void PutString(string str)
        {
            PutString(_position, str, Encoding.Unicode);
            _position += str.Length * 2 + 2;
        }

        /// <summary>
        /// Puts null terminated unicode string into packet buffer
        /// </summary>
        /// <param name="str"></param>
        /// <param name="encoding"></param>
        public void PutString(string str, Encoding encoding)
        {
            PutString(_position, str, encoding);
            _position += str.Length * (encoding.IsSingleByte ? 1 : 2) + 2;
        }

        /// <summary>
        /// Put string with fixed length into packet buffer
        /// </summary>
        public void PutFixedString(int index, string str, Encoding encoding, int fixedSize)
        {
            var src = encoding.GetBytes(str);
            if (src.Length > fixedSize)
                throw new Exception("Can't put fixed string cause string byte size are bigger than requested fixed size");

            var arr = new byte[fixedSize];
            Buffer.BlockCopy(src, 0, arr, 0, src.Length);

            PutArray(index, arr);
        }

        /// <summary>
        /// Put string with fixed length into packet buffer
        /// </summary>
        public void PutFixedString(string str, Encoding encoding, int fixedSize)
        {
            PutFixedString(_position, str, encoding, fixedSize);
            _position += fixedSize;
        }

        /// <summary>
        /// Put string with fixed length into packet buffer
        /// </summary>
        public void PutFixedString(string str, int fixedSize)
        {
            PutFixedString(_position, str, Encoding.Unicode, fixedSize);
            _position += fixedSize;
        }

        public void Skip(int size)
        {
            ResizeIf2Small(ref Payload, _position + size);
            _position += size;
        }

        /// <summary>
        /// Read packet after receive
        /// This method must be overrided by incoming packet models
        /// </summary>
        /// <returns>Was read operation successfull</returns>
        public virtual bool ProcessRead()
        {
            Log.Warn("Packet '{0}' empty read!", GetType().Name);
            return false;
        }

        /// <summary>
        /// Pass actions to engine
        /// </summary>
        /// <param name="receiver"></param>
        public virtual void PassToEngine(AALClient receiver)
        {
            Log.Warn("Packet '{0}' empty pass to engine!", GetType().Name);
        }


        /// <summary>
        /// Read packet after receive
        /// This method must be overrided by outcoming packet models
        /// </summary>
        public virtual void ProcessWrite()
        {
            Log.Warn("Packet '{0}' empty write!", GetType().Name);
        }

        /// <summary>
        /// Send <code>this</code> frame to target client
        /// </summary>
        /// <param name="client"></param>
        /// <param name="useCheck"></param>
        public virtual void Send(AALClient client, byte useCheck = 0)
        {
            ProcessWrite();

            Debug.WriteLine("Send packet:'{0}':\n{1}", GetType().Name, Payload.ToHex());

            client.Send(this, useCheck);
        }

        /// <summary>
        /// Resize buffer if current size is to small
        /// Write position will not change
        /// </summary>
        /// <param name="array"></param>
        /// <param name="newSize"></param>
        public static void ResizeIf2Small(ref byte[] array, int newSize)
        {
            if (Buffer.ByteLength(array) < newSize)
                Array.Resize(ref array, newSize);
        }
    }
}
